<?php

declare(strict_types=1);

namespace Hyperfx\Framework\Command;

use Hyperf\Command\Annotation\Command;
use Hyperf\Command\Command as HyperfCommand;
use Hyperf\Utils\CodeGen\Project;
use Hyperf\Utils\Composer;
use Hyperf\Utils\Str;
use Psr\Container\ContainerInterface;
use Swoole\ExitException;
use Symfony\Component\Console\Input\InputOption;

/**
 * @Command
 */
#[Command]
class GenServiceCodeCommand extends HyperfCommand
{
    /**
     * @var ContainerInterface
     */
    protected $container;

    public function __construct(ContainerInterface $container)
    {
        $this->container = $container;

        parent::__construct('gen:grpc-service-code');
    }

    public function configure()
    {
        $this->setDescription('生产Grpc服务层代码 Command');
        $this->addOption('grpc-client', 'gc', InputOption::VALUE_REQUIRED, 'grpc client 客户端代码, 如: \Grpc\Base\Common\V1\CommonClient::class', \Grpc\Base\Common\V1\CommonClient::class);
        $this->addOption('namespace', 'ns', InputOption::VALUE_OPTIONAL, 'grpc index namespace', 'App\Grpc');
        $this->addOption('service', 'svc', InputOption::VALUE_OPTIONAL, 'grpc service namespace', 'App\Service');
    }

    public function handle()
    {
        $grpcClientClass = $this->className($this->getGrpcClientInput());
        $project = new Project();

        $class = new \ReflectionClass($grpcClientClass);
        $methods = $class->getMethods(\ReflectionMethod::IS_PUBLIC);
        $parsedMethodMaps = [];
        foreach ($methods as $method) {
            if (str_starts_with($method->getName(), '_') || 0 == strcmp($method->getName(),  'create')) {
                continue;
            }
            preg_match('/@return (.*)/', $method->getDocComment(), $arrDoc);
            $response = $arrDoc[1] ?? '';
            $response = trim($response);
            if (!empty($response) && str_starts_with($response, '\\')) {
                $response = substr($response, 1);
            }
            $parsedMethodMaps[$method->getName()] = [
                'name' => $method->getName(),
                "response" => trim($response),
            ];
        }

        [$package, $service, $namespacePrefix] = $this->parsePackageAndService($grpcClientClass);

        // 生成grpc入口类
        $grpcStub = $this->buildGrpcClass($parsedMethodMaps, $package, $service, $namespacePrefix);
        $grpcClass = sprintf('%s\\%s', $this->input->getOption('namespace'), $service . 'Grpc');
        $grpcFile = BASE_PATH . DIRECTORY_SEPARATOR . $project->path($grpcClass);
        if (! file_exists($grpcFile)) {
            $this->mkdir($grpcFile);
        }
        file_put_contents($grpcFile, $grpcStub);
        $this->line(sprintf('Gen[%s] successfully', $grpcFile), 'info');


        // 生成service code
        $service = $this->input->getOption('service');
        foreach ($parsedMethodMaps as $method) {
            $serviceStub = $this->buildGrpcServiceClass($method, $namespacePrefix);
            $serviceClass = $service . '\\' . $method['name'] . 'Service';
            $serviceFile = BASE_PATH . DIRECTORY_SEPARATOR . $project->path($serviceClass);
            if (! file_exists($serviceFile)) {
                $this->mkdir($serviceFile);
                file_put_contents($serviceFile, $serviceStub);
                $this->line(sprintf('Gen[%s] successfully', $serviceFile), 'info');
            }
        }
    }

    public function className(string $path): string
    {
        if (str_starts_with($path, './')) {
            $path = str_replace('./', '', $path);
        }
        $ext = pathinfo($path, PATHINFO_EXTENSION);

        if ($ext !== '') {
            $path = substr($path, 0, -(strlen($ext) + 1));
        } else {
            $path = trim($path, '/') . '/';
        }

        foreach ($this->getAutoloadRules() as $prefix => $prefixPath) {
            if ($this->isRootNamespace($prefix) || strpos($path, $prefixPath) === 0) {
                $name = substr($path, strlen($prefixPath));
                return substr($prefix, 0, -1) . str_replace('/', '\\', $name);
            }
        }
        throw new ExitException("Invalid project path: {$path}");
    }

    protected function isRootNamespace(string $namespace): bool
    {
        return $namespace === '';
    }

    protected function getAutoloadRules(): array
    {
        return data_get(Composer::getJsonContent(), 'autoload.psr-4', []);
    }

    protected function mkdir(string $path): void
    {
        $dir = dirname($path);
        if (! is_dir($dir)) {
            @mkdir($dir, 0755, true);
        }
    }


    private function parsePackageAndService(string $name): array
    {
        $pos = strripos($name, '\\');

        $namespacePrefix = substr($name, 0, $pos);
        $package = strtolower(str_replace('\\', '.', $namespacePrefix));

        $serviceClient = substr($name, $pos + 1);
        $service = str_replace('Client', '', $serviceClient);

        return [$package, $service, $namespacePrefix];
    }

    protected function buildGrpcClass(array $parsedMethodMaps, $package, $service, $namespacePrefix): string
    {
        $namespace = $this->input->getOption('namespace');
        $stub = file_get_contents(__DIR__ . '/stubs/grpc.stub');

        $stubMethod = file_get_contents(__DIR__ . '/stubs/grpc_method.stub');

        // parse uses
        $uses = [];
        $methodsString = '';
        foreach ($parsedMethodMaps as $method) {
            $uses[] = sprintf('%s\\%s%s', $this->input->getOption('service'), $method['name'], 'Service');
            $uses[] = sprintf('%s\\%s%s', $namespacePrefix, $method['name'], 'Request');
            $uses[] = $method['response'];
            $shortResponse = self::getMethodByPackage($method['response']);

            // 替换grpc的method
            $_methodString = $this->makeGrpcMethod($stubMethod, $method['name']);
            $methodsString .= $this->makeResponse($_methodString, $shortResponse);
        }

        $uses = array_unique($uses);

        // replace
        $this->replaceNamespace($stub, $namespace)
            ->replacePackage($stub, $package)
            ->replaceService($stub, $service)
            ->replaceUses($stub, join(",\n    ", $uses))
            ->replaceMethods($stub, $methodsString);

        return $stub;
    }

    private static function getMethodByPackage(string $package): string {
        $endIndex = strrpos($package, '\\');
        return substr($package, $endIndex + 1);
    }

    protected function buildGrpcServiceClass(array $method, $namespacePrefix): string
    {
        $namespace = $this->input->getOption('service');
        $stub = file_get_contents(__DIR__ . '/stubs/grpc_service.stub');

        $uses = [
            sprintf('%s\%s%s', $namespacePrefix, $method['name'], 'Request'),
            $method['response']
        ];

        $shortResponse = self::getMethodByPackage($method['response']);

        // replace
        $this->replaceNamespace($stub, $namespace)
            ->replaceUses($stub, join(",\n    ", $uses))
            ->replaceResponse($stub, $shortResponse)
            ->replaceMethod($stub, $method['name']);

        return $stub;
    }

    protected function replaceUses(string &$stub, string $uses): self
    {
        $uses = $uses ? "use {$uses};" : '';
        $stub = str_replace(
            ['%USES%'],
            [$uses],
            $stub
        );

        return $this;
    }

    protected function replaceNamespace(&$stub, $name)
    {
        $stub = str_replace(
            ['%NAMESPACE%'],
            [$name],
            $stub
        );

        return $this;
    }

    protected function replaceMethods(&$stub, $name)
    {
        $stub = str_replace(
            ['%METHODS%'],
            [$name],
            $stub
        );

        return $this;
    }

    protected function replaceMethod(&$stub, $name)
    {
        $stub = str_replace(
            ['%METHOD%'],
            [$name],
            $stub
        );

        return $this;
    }

    protected function replacePackage(&$stub, $name)
    {
        $stub = str_replace(
            ['%PACKAGE%'],
            [$name],
            $stub
        );

        return $this;
    }

    protected function makeGrpcMethod($stub, $name)
    {
        return str_replace(
            ['%METHOD%'],
            [$name],
            $stub
        );
    }

    protected function replaceService(&$stub, $name)
    {
        $stub = str_replace(
            ['%SERVICE%'],
            [$name],
            $stub
        );

        return $this;
    }

    protected function makeResponse(&$stub, $name)
    {
        return str_replace(
            ['%RESPONSE%'],
            [$name],
            $stub
        );
    }

    protected function replaceResponse(&$stub, $name)
    {
        $stub = str_replace(
            ['%RESPONSE%'],
            [$name],
            $stub
        );

        return $this;
    }

    protected function getGrpcClientInput(): string
    {
        $input = $this->input->getOption('grpc-client');
        if (!$input) {
            $this->error('The "--grpc-client" option must be set.');
            throw new ExitException('The "--grpc-client" option must be set.', 1);
        }
        return trim($input);
    }
}
